package com.gmrz.uaf.server;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.service.challengestore.ChallengeStoreService;
import com.gmrz.service.logpool.LogPoolManager;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.common.UAFRuntimeException;
import com.gmrz.uaf.common.service.challengestore.UAFChallengeStoreServiceProvider;
import com.gmrz.uaf.crypto.CryptoEngine;
import com.gmrz.uaf.crypto.spi.handlers.Operations;
import com.gmrz.uaf.db.DAOException;
import com.gmrz.uaf.db.UAFDAOFactory;
import com.gmrz.uaf.db.UAFDBConnectionMgr;
import com.gmrz.uaf.db.dao.AuthenticatorSpecDAO;
import com.gmrz.uaf.db.dao.BaseUasConfigDAO;
import com.gmrz.uaf.db.dao.UasConfigDAO;
import com.gmrz.uaf.policy.PolicyManager;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.AuthenticatorSpec;
import com.gmrz.uaf.protocol.v1.validaton.PolicyValidationException;
import com.gmrz.util.db.DBUtil;
import com.google.inject.Injector;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.io.IOException;
import java.io.InputStream;
import java.security.Security;
import java.sql.Connection;
import java.util.*;


public class UAFServletContextListener
        implements ServletContextListener {
    private static final Logger LOG = LogManager.getLogger(UAFServletContextListener.class);

    private ServerConfig serverConfig = null;
    private ChallengeStoreService challengeStoreService = null;
    private LogPoolManager logPoolManager = null;
    private ServletContextEvent servletContextEvent = null;

    public void contextDestroyed(ServletContextEvent arg0) {
        cleanUpCoreServer();
    }

    public void contextInitialized(ServletContextEvent arg0) {
        servletContextEvent = arg0;
        initializeCoreServer();
    }

    private void initializeCoreServer() {
        LOG.info("Initializing core server components...");
        try {
            Security.addProvider(new BouncyCastleProvider());
            // 连接数据库
            UAFDBConnectionMgr.init();
            LOG.info("Database connection initialized.");
            // as配置信息获取（例如 redis 的连接配置信息等）
            Properties uasconfig = InitializeUasConfig();
            GuiceUtil.setUafProperties(uasconfig);
            // 初始化加密服务
            initializeCryptoEngine();
            // 实现对象的绑定，并返回 Injector 对象，可以使用 Injector 对象获取绑定的实例
            Injector injector = GuiceUtil.getProcessorInjector();
            // 从 injector 中获取ServerConfig 对象
            this.serverConfig = injector.getInstance(ServerConfig.class);
            // 检查服务是否启动成功
            checkLicenseAccept();
            LOG.info("License check done.");
            // 初始化挑战值服务
            initializeChallengeStoreService(injector);
            // 初始化日志管理
            initiliazeLogManager(injector);
            // 初始化策略校验
            validatePolicies(injector);
            //MetricsUtil.startMetrics();
            // 初始化客户名称，为证书授权校验使用
            InitializeCustomerName();
        } catch (Exception e) {
            LOG.error("UAF Server initialization failed due to - ", e);
            cleanUpCoreServer();
            throw new UAFRuntimeException(e);
        }
    }

    private Properties InitializeUasConfig() throws DAOException {
        LOG.info("InitializeUasConfig start1");
        Connection connection = null;
        Connection connection2 = null;
        LOG.info("InitializeUasConfig start2");
        Map<String, String> facetMap = new HashMap<String, String>();
        Properties uasconfigProp = new Properties();
        LOG.info("InitializeUasConfig start3");
        try {
            connection = UAFDBConnectionMgr.getConnection(true);
            UasConfigDAO dao = new BaseUasConfigDAO(connection);

            connection2 = UAFDBConnectionMgr.getConnection(true);
            UasConfigDAO dao2 = new BaseUasConfigDAO(connection2);

            //GuiceUtil.getProcessorInjector().getInstance(DAOFactory.class).createUasConfigDAO(connection);
            //dao.conn_=connection;
            LOG.info("InitializeUasConfig start");
            uasconfigProp = dao.loadUasConfig();
            facetMap = dao2.loadFacetsTenantID();
            if (facetMap != null || !facetMap.isEmpty()) {
//             LOG.error("Failed to initialize the facet list,please check facet configuration in database");
//             throw new UAFRuntimeException("Failed to initialize the facet list.");
                uasconfigProp.put("uaf.facet.id", facetMap.toString());
            }
            InputStream inputStream = UAFServletContextListener.class.getClassLoader().getResourceAsStream("/redis.properties");
            LOG.info("redis config file ");
            if (null != inputStream) {
                LOG.info("redis config file start...");
                //FileInputStream inputStream = new FileInputStream(redisConfigFile);
                Properties redisProperties = new Properties();
                redisProperties.load(inputStream);
                uasconfigProp.setProperty(Constants.REDIS_TIME_OUT, redisProperties.getProperty(Constants.REDIS_TIME_OUT));
                uasconfigProp.setProperty(Constants.REDIS_DATA_NUMBER, redisProperties.getProperty(Constants.REDIS_DATA_NUMBER));
                uasconfigProp.setProperty(Constants.REDIS_ENCRYPT, redisProperties.getProperty(Constants.REDIS_ENCRYPT));
                uasconfigProp.setProperty(Constants.REDIS_MASTER_NAME, redisProperties.getProperty(Constants.REDIS_MASTER_NAME));
                uasconfigProp.setProperty(Constants.REDIS_PASSWORD, redisProperties.getProperty(Constants.REDIS_PASSWORD));
                uasconfigProp.setProperty(Constants.REDIS_SERVERS, redisProperties.getProperty(Constants.REDIS_SERVERS));

                LOG.debug("redis config file " + Constants.REDIS_TIME_OUT + ":" + redisProperties.getProperty(Constants.REDIS_TIME_OUT));
                LOG.debug("redis config file " + Constants.REDIS_DATA_NUMBER + ":" + redisProperties.getProperty(Constants.REDIS_DATA_NUMBER));
                LOG.debug("redis config file " + Constants.REDIS_ENCRYPT + ":" + redisProperties.getProperty(Constants.REDIS_ENCRYPT));
                LOG.debug("redis config file " + Constants.REDIS_MASTER_NAME + ":" + redisProperties.getProperty(Constants.REDIS_MASTER_NAME));
                LOG.debug("redis config file " + Constants.REDIS_PASSWORD + ":" + redisProperties.getProperty(Constants.REDIS_PASSWORD));
                LOG.debug("redis config file " + Constants.REDIS_SERVERS + ":" + redisProperties.getProperty(Constants.REDIS_SERVERS));

                LOG.debug("redis config file end...");
            }

            String appId = uasconfigProp.getProperty("uaf.application.id");
            if (StringUtils.isEmpty(appId)) {
                uasconfigProp.setProperty("uaf.application.id", "");
            }

        } catch (Exception sqle) {
            LOG.error("InitializeUasConfig error",sqle);
            throw new DAOException(UAFErrorCode.DB_DATABASE_CONNECTION_ERROR, sqle);
        } finally {
            DBUtil.close(connection);
            DBUtil.close(connection2);
        }
        return uasconfigProp;
    }

    private void initializeCryptoEngine() {
        LOG.info("Initializing CryptoEngine ...");
        CryptoEngine cryptoEngine = GuiceUtil.getProcessorInjector().getInstance(CryptoEngine.class);
        if (cryptoEngine == null) {
            throw new UAFRuntimeException("Failed to initialize the crypto layer.");
        }
        cryptoEngine.initialize(Operations.getAllOperations());
        LOG.info("CryptoEngine initialization done.");
    }

    private void initiliazeLogManager(Injector injector) {
        LOG.info("Initialized LogManager ...");
        logPoolManager = injector.getInstance(LogPoolManager.class);
        if (logPoolManager == null) {
            throw new UAFRuntimeException("Failed to initialize LogPoolManager");
        }
        logPoolManager.init();
        LOG.info("LogPoolManager initialization done.");
    }

    private void checkLicenseAccept() {
        boolean licenseAccept = this.serverConfig.getLicenseAccept();
        if (licenseAccept != true) {
            String errorMessage = "Server failed to start up. To fix this error, accept license by setting: license.accept to true in uaf.properties configuration file.";
            LOG.fatal(errorMessage);
            throw new UAFRuntimeException(errorMessage);
        }
    }

    private void initializeChallengeStoreService(Injector injector) throws DAOException {
        if (this.challengeStoreService == null) {
            UAFChallengeStoreServiceProvider uafChallengeStoreServiceProvider = injector.getInstance(UAFChallengeStoreServiceProvider.class);

            this.challengeStoreService = uafChallengeStoreServiceProvider.get();
            if (this.challengeStoreService == null) {
                throw new UAFRuntimeException("Unable to initialize the challenge store service");
            }
            LOG.info("UAFChallengeStoreService initialized.");
        } else {
            LOG.info("UAFChallengeStoreService is already initialized.");
        }
        clearCache();
    }

    private void clearCache(){
        Connection conn = null;
        Connection conn2 = null;
        try {
            List<String> keyList = new ArrayList<String>();
            keyList.add(Constants.WHITE_LIST_KEY);
            keyList.add(Constants.UAP_UVS_MATCH_CACHE_KEY);
            keyList.add(Constants.UAP_APP_ID_CACHE_KEY);
            keyList.add(Constants.UAP_EXACT_MATCH_CACHE_KEY);
            keyList.add(Constants.CERT_NUM);
            keyList.add(Constants.UAF_VENDOR_ACCEPT);
            keyList.add(Constants.UAF_FIDO_LOG_SWITCH);
            keyList.add(Constants.UAS_CONFIG_KEY_UAF_REG_STATUS);
            keyList.add(Constants.UAS_CONFIG_KEY_UAF_AUTH_STATUS);
            keyList.add(Constants.LOCK_VALUE);
            keyList.add(Constants.LOCK_VALUE);
            keyList.add(Constants.UAP_SPEC_LIST);

            conn = UAFDBConnectionMgr.getConnection(true);
            conn2 = UAFDBConnectionMgr.getConnection(true);
            AuthenticatorSpecDAO specdao = UAFDAOFactory.createAuthenticatorSpecDAO(conn);
            List<AuthenticatorSpec> specList = specdao.list();

            if(null != specList){
                for(AuthenticatorSpec authenticatorSpec : specList) {
                    keyList.add(Constants.UAP_SPEC_AAID+authenticatorSpec.getAAID());
                    keyList.add(Constants.UAP_SPEC_ID+authenticatorSpec.getID());
                }
            }

            List<String> tenantids = UAFDAOFactory.createUasConfigDAO(conn2).getTenantids();
            if(null != tenantids){
                for(String tenantId : tenantids){
                    keyList.add(Constants.CACHE_VENDOR_ALLOW+tenantId);
                    keyList.add(Constants.CACHE_EQUIPMENT_FORBID+tenantId);
                    keyList.add(Constants.CACHE_DEVICE_FORBID+tenantId);
                    keyList.add(Constants.UAP_APP_ID+tenantId);
                    keyList.add(Constants.UAP_EXACT_MATCH_CACHE_KEY+tenantId);
                }
            }

            keyList.add("uaf.004A#01B0.plugin.json");
            keyList.add("uaf.004A#01B1.plugin.json");
            keyList.add("uaf.004A#01B2.plugin.json");
            keyList.add("uaf.004A#01B3.plugin.json");
            keyList.add("uaf.certifica.plugin.json");
            keyList.add("uaf.tenant.share");
            keyList.add("uaf.cert.auth.info");
            keyList.add("uaf.facet.id");
            keyList.add("uaf.policy.json");
            keyList.add("uaf.cert.transaction.length");
            for(Constants.AuthType authType : Constants.AuthType.values()){
                keyList.add(Constants.UAF_DELETE_LAST_AUTHENTICATOR+authType.getAuthType());
                keyList.add(Constants.UAF_DELETE_OTHER_AUTHENTICATOR +authType.getAuthType());
            }

            keyList.add(Constants.UAF_NOT_ACTIVITY_AUTHENTICATOR);
            keyList.add(Constants.UAF_ACTIVITY_ONLY_AUTHENTICATOR);


            for (String key : keyList) {
                this.challengeStoreService.deleteRedisValue(key);
            }

        }catch (Exception e){
            throw new UAFRuntimeException(e);
        }finally {
            DBUtil.close(conn);
            DBUtil.close(conn2);
        }

    }



    private void validatePolicies(Injector injector) throws PolicyValidationException {
        LOG.info("Validating policies in policy store");
        PolicyManager policyMgr = injector.getInstance(PolicyManager.class);
        policyMgr.validatePolicies();
        LOG.info("Policy validation completed.");
    }

    private void cleanUpCoreServer() {
        LOG.info("Cleaning up core server components.");
        try {
            if (this.challengeStoreService != null) {
                this.challengeStoreService.shutdown();
            }
            if (logPoolManager != null)
                logPoolManager.shutdown();
        } catch (ChallengeStoreException e) {
            LOG.error(e);
            throw new UAFRuntimeException(e);
        } catch (Exception e) {
            LOG.error(e);
        }
    }



    private void InitializeCustomerName() throws IOException {
        LOG.info("InitializeCustomerName start1");
        InputStream inputStream = UAFServletContextListener.class.getClassLoader().getResourceAsStream("/application.properties");
        LOG.info("application config file ");
        if (null != inputStream) {
            LOG.info("application config file start...");
            Properties applicationProperties = new Properties();
            applicationProperties.load(inputStream);
            Constants.UAF_CERT_AUTH_CUSTOMER_NAME_VALUE = applicationProperties.getProperty(Constants.UAF_CERT_AUTH_CUSTOMER_NAME_KEY);
            LOG.debug("application config file end...");
        }
    }
}
